import { AlignType, ClassName, CtrlType, Events, FontFamily, LinkMark, LinkStyle } from "../../../Constants";
import { IPixelPos, IPoint, IRelativePos, IVctor } from "../../../Interface";
import { Utils } from "../../../utils";
import Feature from "../../Feature";
import Line from "../Line";
import AStar from "./AStar";
import { computedProbablyPoints } from "./utils";
import Rect from "../Rect";
const aStar = new AStar();
let flowIndex = 0;

// 连接线
export default class Link extends Line {

    _linkStyle: LinkStyle = LinkStyle.DEFAULT;
    _startFeature: Feature | null = null;   // 如果你要设置为点坐标,你可以通过setTarget去更新link
    get startFeature() { return this._startFeature }
    set startFeature(val) {
        this._startFeature = val;
        if (val) this.setTarget(val, LinkMark.START)
    }
    _endFeature: Feature | null = null;
    get endFeature() { return this._endFeature }
    set endFeature(val) {
        this._endFeature = val;
        if (val) this.setTarget(val, LinkMark.START)
    }
    isFlowSegment: boolean = false;
    isDelWithFeature: boolean = true;   // 删除元素时,是否同时删除自己link线

    _autoEdgeFlag: boolean = false;   // 是否是自动获取包围盒矩形边的中点作为起点或终点, 一个标识属性主动设置true或false没意义
    _onTranslateStart = this.onTranslateStart.bind(this)
    _onTranslateEnd = this.onTranslateEnd.bind(this)
    _onDelete = () => { }

    // 如果是传的是点,那么可能无法通过拖拽更新link的位置
    constructor(startFeature: Feature | IRelativePos, endFeature: Feature | IRelativePos) {
        let startPos: IRelativePos = { x: 0, y: 0 };
        let endPos: IRelativePos = { x: 0, y: 0 };
        // 是Feature元素则获取元素的中心点
        startFeature instanceof Feature ? startPos = Feature.getCenterPos(startFeature.pointArr) : startPos = startFeature as IRelativePos;
        endFeature instanceof Feature ? endPos = Feature.getCenterPos(endFeature.pointArr) : endPos = endFeature as IRelativePos;
        super([startPos, endPos]);

        this.className = ClassName.LINK
        this.strokeStyle = "#000";
        this.hoverStyle = "#333";
        this.zIndex = -1;
        this.lineWidth = .2
        this.cbDrag = false; // bbox也会等于link的cbDrag,从而不会被主动拖拽,只可以被动的translate
        this.cbTransform = true;
        this.ctrlTypes = [CtrlType.BEZIER_CTRL];  // 可用的控制点类型有哪些

        this._onDelete = () => { Feature.Gls.removeFeature(this) }
        this.setTarget(startFeature, LinkMark.START)
        this.setTarget(endFeature, LinkMark.END)

        // this.label = {
        //     content: '测试文本',
        //     fontSize: 1.7,
        //     color: "#000",
        //     offset: { x: 0, y: 0 },
        //     fontFamily: FontFamily.HEITI,
        //     bolder: false,
        //     // fill: "#000",
        //     autoRotate: true,
        //     fillPadding: 2,
        // };
    }

    override draw(ctx: CanvasRenderingContext2D, pointArr: IPixelPos[], lineWidth: number, lineDashArr: number[]) {
        const path = super.draw(ctx, pointArr, lineWidth, lineDashArr);
        const flowIndex = this.getFlowIndex(pointArr.length, .1);
        this.drawFlowSegment(ctx, pointArr, lineWidth, flowIndex);
        return path;
    }

    onTranslateStart() {
        const feature = this.startFeature as Feature
        this.pointArr[0] = Feature.getCenterPos(feature.pointArr);
        if (this.getGeneratePointArrFn) {
            const originPnts = this.pointArr.filter(f => !f.generated);
            this.pointArr = this.getGeneratePointArrFn(originPnts);
        }
    }
    onTranslateEnd() {
        const feature = this.endFeature as Feature;
        this.pointArr[this.pointArr.length - 1] = Feature.getCenterPos(feature.pointArr)
        if (this.getGeneratePointArrFn) {
            const originPnts = this.pointArr.filter(f => !f.generated);
            this.pointArr = this.getGeneratePointArrFn(originPnts);
        }
    }

    setPntOfBezier(index: number, point: IPoint) {
        if (!this.cbTransform) return console.warn("cbTransform为false, 无法设置控制点喔");
        this.bezierPnts[index].x = point.x;
        this.bezierPnts[index].y = point.y;
        if (this.getGeneratePointArrFn) {
            const originPnts = this.pointArr.filter(f => !f.generated);
            this.pointArr = this.getGeneratePointArrFn(originPnts);
        }
    }

    getGeneratePointArr(linkStyle: LinkStyle, ...args: any) {
        if(this.linkStyle ! = linkStyle) this.bezierPnts = []
        switch (linkStyle) {
            case LinkStyle.CURVE2_HORIZINAL:
                this._autoEdgeFlag = false;
                this.getGeneratePointArrFn = this.getBezier2CurvePoints;
                break;
            case LinkStyle.CURVE2_VERTIVAL:
                this._autoEdgeFlag = false;
                this.getGeneratePointArrFn = () => this.getBezier2CurvePoints(this.pointArr, AlignType.VERTICAL);
                break;
            case LinkStyle.CURVE3_HORIZINAL:
                this._autoEdgeFlag = false;
                this.getGeneratePointArrFn = this.getBezier3CurvePoints;
                break;
            case LinkStyle.CURVE3_VETIVAL:
                this._autoEdgeFlag = false;
                this.getGeneratePointArrFn = () => this.getBezier3CurvePoints(this.pointArr, AlignType.VERTICAL);
                break;
            case LinkStyle.BROKEN2:
                this._autoEdgeFlag = false;
                this.getGeneratePointArrFn = this.getBroken2Points;
                break;
            case LinkStyle.BROKEN1:
                this._autoEdgeFlag = false;
                this.getGeneratePointArrFn = this.getBrokenPoints;
                break;
            case LinkStyle.AUTOEDGE_STRAIGHT:
                this._autoEdgeFlag = true;
                this.cbTransform = false;
                this.getGeneratePointArrFn = this.getAutoAnchor2Points;
                break;
            case LinkStyle.AUTOEDGE_CURVE:
                this._autoEdgeFlag = true;
                this.cbTransform = false;
                this.getGeneratePointArrFn = this.getAutoAnchor2CuervePoints;
                break;
            case LinkStyle.AUTOEDGE_BROKEN2:
                this._autoEdgeFlag = true;
                this.cbTransform = false;
                this.getGeneratePointArrFn = this.getAutoAnchor2Broken2Points;
                break;
            case LinkStyle.AUTOEDGE_BROKEN_DIAGONAL:
                this._autoEdgeFlag = true;
                this.cbTransform = false;
                this.getGeneratePointArrFn = this.getAutoAnchor2DiagonalPoints;
                break;
            case LinkStyle.FIXEDEDGE_ASTAR:
                this._autoEdgeFlag = true;
                this.cbTransform = false;
                this.getGeneratePointArrFn = () => this.getAstarBrokenPoints(...args);
                break;
            default:
                break;
        }
        if (this.getGeneratePointArrFn) {
            const originPnts = this.pointArr.filter(f => !f.generated);
            return this.getGeneratePointArrFn(originPnts, ...args);
        }
        return []
    }

    // 修改起点或终点的位置,切换起始点或终点    
    setTarget(feature: Feature | IRelativePos, type: LinkMark = LinkMark.START) {
        switch (type) {
            case LinkMark.START: {
                this.startFeature?.off(Events.TRANSLATE, this._onTranslateStart)
                this.startFeature?.off(Events.ROTATE, this._onTranslateStart)
                this.startFeature?.off(Events.RESIZE, this._onTranslateStart)
                this.startFeature?.off(Events.DELETE, this._onDelete)
                this.startFeature = null;
                if (feature instanceof Feature) {
                    const center = Feature.getCenterPos(feature.pointArr);
                    this._startFeature = feature;
                    this.pointArr[0] = center;
                    feature.on(Events.TRANSLATE, this._onTranslateStart)
                    feature.on(Events.ROTATE, this._onTranslateStart)
                    feature.on(Events.RESIZE, this._onTranslateStart)
                    this.isDelWithFeature && feature.on(Events.DELETE, this._onDelete)
                } else {
                    this.pointArr[0] = feature;
                }
            }
                break;
            case LinkMark.END: {
                this.endFeature?.off(Events.TRANSLATE, this._onTranslateEnd)
                this.endFeature?.off(Events.ROTATE, this._onTranslateEnd)
                this.endFeature?.off(Events.RESIZE, this._onTranslateEnd)
                this.endFeature?.off(Events.DELETE, this._onDelete)
                this.endFeature = null;
                if (feature instanceof Feature) {
                    const center = Feature.getCenterPos(feature.pointArr);
                    this._endFeature = feature;
                    this.pointArr[this.pointArr.length - 1] = center;
                    feature.on(Events.TRANSLATE, this._onTranslateEnd)
                    feature.on(Events.ROTATE, this._onTranslateEnd)
                    feature.on(Events.RESIZE, this._onTranslateEnd)
                    this.isDelWithFeature && feature.on(Events.DELETE, this._onDelete)
                } else {
                    this.pointArr[this.pointArr.length - 1] = feature;
                }
            }
                break;
            default:
                break;
        }
        if (this.getGeneratePointArrFn) {
            const originPnts = this.pointArr.filter(f => !f.generated);
            this.pointArr = this.getGeneratePointArrFn(originPnts);
        }
    }

    getBezier2CurvePoints(pointArr: IPoint[] = this.pointArr, type: AlignType = AlignType.HORIZONAL, ctrlExtent = 1.5, pntsLimit = 50): IPoint[] {
        let vct: IVctor, cp1: IPoint;
        const startPos = pointArr[0]
        const endPos = pointArr[pointArr.length - 1]
        if (this.bezierPnts.length > 0 && this.cbTransform && !this._autoEdgeFlag) {
            cp1 = this.bezierPnts[0];
        } else {
            const lineVct = Utils.getVector(startPos, endPos);
            switch (type) {
                default:
                case AlignType.HORIZONAL: {
                    vct = Utils.getRotateVct(lineVct, 90);
                    cp1 = Utils.getPntInVct(Utils.getMidOfTwoPnts(startPos, endPos), vct, (startPos.y - endPos.y) / ctrlExtent);
                    break;
                }
                case AlignType.VERTICAL: {
                    vct = Utils.getRotateVct(lineVct, -90);
                    cp1 = Utils.getPntInVct(Utils.getMidOfTwoPnts(startPos, endPos), vct, (startPos.x - endPos.x) / ctrlExtent);
                }
                    break;
            }
            this.bezierPnts[0] = cp1;
        }
        const points = Utils.getPntsOf2Bezier(startPos, cp1, endPos, pntsLimit).map((p, j) => ({ x: p.x, y: p.y, generated: j != 0 })) as IPoint[];
        points.push(endPos);
        return points;
    }

    getBezier3CurvePoints(pointArr: IPoint[] = this.pointArr, type: AlignType = AlignType.HORIZONAL, ctrlExtent = 1.5, pntsLimit = 50): IPoint[] {
        let vct: IVctor, cp1: IPoint, cp2: IPoint;
        const startPos = pointArr[0]
        const endPos = pointArr[pointArr.length - 1]
        if (this.bezierPnts.length > 0 && this.cbTransform && !this._autoEdgeFlag) {
            cp1 = this.bezierPnts[0];
            cp2 = this.bezierPnts[1];
        } else {
            switch (type) {
                default:
                case AlignType.VERTICAL: {
                    vct = [0, -1] as IVctor;
                    cp1 = Utils.getPntInVct(startPos, vct, (startPos.y - endPos.y) / ctrlExtent);
                    cp2 = Utils.getPntInVct(endPos, vct, (endPos.y - startPos.y) / ctrlExtent);
                    break;
                }
                case AlignType.HORIZONAL: {
                    vct = [-1, 0] as IVctor;
                    cp1 = Utils.getPntInVct(startPos, vct, (startPos.x - endPos.x) / ctrlExtent);
                    cp2 = Utils.getPntInVct(endPos, vct, (endPos.x - startPos.x) / ctrlExtent);
                }
                    break;
            }
            this.bezierPnts[0] = cp1;
            this.bezierPnts[1] = cp2;
        }
        const points = Utils.getPntsOf3Bezier(startPos, cp1, cp2, endPos, pntsLimit).map((p, j) => ({ x: p.x, y: p.y, generated: j != 0 })) as IPoint[];
        points.push(endPos);
        return points;
    }

    getBroken2Points(pointArr: IPoint[] = this.pointArr, type: AlignType = AlignType.HORIZONAL, ctrlExtent = .5): IPoint[] {
        let cp1: IPoint, cp2: IPoint;
        const startPos = pointArr[0]
        const endPos = pointArr[pointArr.length - 1]
        const mid = Utils.getPointOnLine(startPos, endPos, ctrlExtent)
        if (this.bezierPnts.length > 0 && this.cbTransform && !this._autoEdgeFlag) {
            cp1 = this.bezierPnts[0];
            cp2 = this.bezierPnts[1];
        } else {
            if (type === AlignType.VERTICAL) {
                cp1 = { x: mid.x, y: startPos.y, generated: true };
                cp2 = { x: mid.x, y: endPos.y, generated: true };
            } else {
                cp1 = { x: startPos.x, y: mid.y, generated: true };
                cp2 = { x: endPos.x, y: mid.y, generated: true };
            }
            this.bezierPnts[0] = cp1;
            this.bezierPnts[1] = cp2;
        }
        return [startPos, cp1, cp2, endPos];
    }

    getBrokenPoints(pointArr: IPoint[] = this.pointArr, type: AlignType = AlignType.HORIZONAL): IPoint[] {
        let cp1: IPoint;
        const startPos = pointArr[0]
        const endPos = pointArr[pointArr.length - 1]
        if (this.bezierPnts.length > 0 && this.cbTransform && !this._autoEdgeFlag) {
            cp1 = this.bezierPnts[0];
        } else {
            if (type === AlignType.VERTICAL) {
                cp1 = { x: endPos.x, y: startPos.y, generated: true };
            } else {
                cp1 = { x: startPos.x, y: endPos.y, generated: true };
            }
            this.bezierPnts[0] = cp1;
        }
        return [startPos, cp1, endPos];
    }

    getAutoAnchor2Points(pointArr: IPoint[] = this.pointArr) {  // 直线
        const startPos = pointArr[0]
        const endPos = pointArr[pointArr.length - 1]
        const angle = Utils.getAngleOfTwoPnts(startPos, endPos) + 90;
        const direction = Utils.getDeriction(angle);
        const [startP, endP] = this.getLinkPosByDirection(direction, this.startFeature || pointArr[0], this.endFeature || pointArr[this.pointArr.length - 1])
        return [startP, endP]
    }

    getAutoAnchor2CuervePoints(pointArr: IPoint[] = this.pointArr, type: AlignType = AlignType.HORIZONAL) {  // Z字型
        const startPos = pointArr[0]
        const endPos = pointArr[pointArr.length - 1]
        const angle = Utils.getAngleOfTwoPnts(startPos, endPos) + 90;
        const direction = Utils.getDeriction(angle);
        const [startP, endP] = this.getLinkPosByDirection(direction, this.startFeature || pointArr[0], this.endFeature || pointArr[this.pointArr.length - 1])
        if (type == AlignType.HORIZONAL) {
            switch (direction) {
                case AlignType.TOP: { return this.getBezier3CurvePoints([startP, endP], AlignType.VERTICAL) }
                case AlignType.RIGHT: { return this.getBezier3CurvePoints([startP, endP], AlignType.HORIZONAL) }
                case AlignType.BOTTOM: { return this.getBezier3CurvePoints([startP, endP], AlignType.VERTICAL) }
                case AlignType.LEFT: { return this.getBezier3CurvePoints([startP, endP], AlignType.HORIZONAL) }
                default: break;
            }
        }
        if (type == AlignType.VERTICAL) {
            switch (direction) {
                case AlignType.TOP: { return this.getBezier3CurvePoints([startP, endP], AlignType.HORIZONAL) }
                case AlignType.RIGHT: { return this.getBezier3CurvePoints([startP, endP], AlignType.VERTICAL) }
                case AlignType.BOTTOM: { return this.getBezier3CurvePoints([startP, endP], AlignType.HORIZONAL) }
                case AlignType.LEFT: { return this.getBezier3CurvePoints([startP, endP], AlignType.VERTICAL) }
                default: break;
            }
        }
        return []
    }

    getAutoAnchor2Broken2Points(pointArr: IPoint[] = this.pointArr) {  // Z字型
        const startPos = pointArr[0]
        const endPos = pointArr[pointArr.length - 1]
        const angle = Utils.getAngleOfTwoPnts(startPos, endPos) + 90;
        const direction = Utils.getDeriction(angle);
        const [startP, endP] = this.getLinkPosByDirection(direction, this.startFeature || pointArr[0], this.endFeature || pointArr[pointArr.length - 1])
        switch (direction) {
            case AlignType.TOP: { return this.getBroken2Points([startP, endP], AlignType.HORIZONAL) }
            case AlignType.RIGHT: { return this.getBroken2Points([startP, endP], AlignType.VERTICAL) }
            case AlignType.BOTTOM: { return this.getBroken2Points([startP, endP], AlignType.HORIZONAL) }
            case AlignType.LEFT: { return this.getBroken2Points([startP, endP], AlignType.VERTICAL) }
            default: break;
        }
        return []
    }

    getAutoAnchor2DiagonalPoints(pointArr: IPoint[] = this.pointArr, length = 1.5) {  // 斜Z字型
        const startPos = pointArr[0]
        const endPos = pointArr[pointArr.length - 1]
        const vctX: IVctor = [10, 0];
        const vctY: IVctor = [0, 10];
        const angle = Utils.getAngleOfTwoPnts(startPos, endPos) + 90;
        const direction = Utils.getDeriction(angle);
        const [startP, endP] = this.getLinkPosByDirection(direction, this.startFeature || this.pointArr[0], this.endFeature || this.pointArr[this.pointArr.length - 1])
        switch (direction) {
            case AlignType.TOP: {
                return [startP, Utils.getPntInVct(startP, vctY, -Feature.Render.getRatioSize(length)), Utils.getPntInVct(endP, vctY, Feature.Render.getRatioSize(length)), endP]
            }
            case AlignType.RIGHT: {
                return [startP, Utils.getPntInVct(startP, vctX, Feature.Render.getRatioSize(length)), Utils.getPntInVct(endP, vctX, -Feature.Render.getRatioSize(length)), endP]
            }
            case AlignType.BOTTOM: {
                return [startP, Utils.getPntInVct(startP, vctY, Feature.Render.getRatioSize(length)), Utils.getPntInVct(endP, vctY, -Feature.Render.getRatioSize(length)), endP]
            }
            case AlignType.LEFT: {
                return [startP, Utils.getPntInVct(startP, vctX, -Feature.Render.getRatioSize(length)), Utils.getPntInVct(endP, vctX, Feature.Render.getRatioSize(length)), endP]
            }
            default:
                break;
        }
        return []
    }

    getAstarBrokenPoints(startEdge = AlignType.TOP, endEdge = AlignType.BOTTOM) {  // 自动寻路        
        // 计算出所有可能的点        
        if (this.startFeature instanceof Rect && this.endFeature instanceof Rect) {
            let { startPoint, endPoint, fakeStartPoint, fakeEndPoint, points } =
                computedProbablyPoints(startEdge, { ...this.startFeature.pointArr[0], ...this.startFeature.getBboxSize(this.startFeature.pointArr) }, endEdge, { ...this.endFeature.pointArr[0], ...this.endFeature.getBboxSize(this.endFeature.pointArr) });
            const routes = aStar.start(fakeStartPoint, fakeEndPoint, points);
            return [startPoint, ...routes, endPoint]
        } return []
    }

    getLinkPosByDirection(direction: AlignType, start: Feature | IPoint, end: Feature | IPoint): IRelativePos[] {
        const sPointArr = start instanceof Feature ? start.pointArr : [start];
        const ePointArr = end instanceof Feature ? end.pointArr : [end];
        const startEdges = Feature.getRectEdges(sPointArr) as IPoint[][];
        const endEdges = Feature.getRectEdges(ePointArr) as IPoint[][];
        switch (direction) {
            case AlignType.TOP: {
                const startP = Utils.getMidOfTwoPnts(startEdges[0][0], startEdges[0][1])
                const endP = Utils.getMidOfTwoPnts(endEdges[2][0], endEdges[2][1])
                return [startP, endP];
            }
            case AlignType.RIGHT: {
                const startP = Utils.getMidOfTwoPnts(startEdges[1][0], startEdges[1][1])
                const endP = Utils.getMidOfTwoPnts(endEdges[3][0], endEdges[3][1])
                return [startP, endP];
            }
            case AlignType.BOTTOM: {
                const startP = Utils.getMidOfTwoPnts(startEdges[2][0], startEdges[2][1])
                const endP = Utils.getMidOfTwoPnts(endEdges[0][0], endEdges[0][1])
                return [startP, endP];
            }
            case AlignType.LEFT: {
                const startP = Utils.getMidOfTwoPnts(startEdges[3][0], startEdges[3][1])
                const endP = Utils.getMidOfTwoPnts(endEdges[1][0], endEdges[1][1])
                return [startP, endP];
            }
            default:
                return [{ x: 0, y: 0 }, { x: 0, y: 0 }];
        }
    }

    // 一个流动的点
    getFlowIndex(endIndex = 0, speed = .3) {
        if (!this.isFlowSegment) return
        if (flowIndex >= (endIndex)) {
            flowIndex = 0;
        } else {
            flowIndex += speed;
        }
        return Math.floor(flowIndex);
    }

    drawFlowSegment(ctx: CanvasRenderingContext2D, pointArr: IPixelPos[], lineWidth = 0, flowIndex = 0) {
        if (!this.isFlowSegment) return
        const path = new Path2D();
        let endIndex = flowIndex + Math.ceil(pointArr.length * .2)
        if (endIndex > pointArr.length - 1) {
            endIndex = pointArr.length - 1;
        }
        const flowPnts = pointArr.slice(flowIndex, endIndex) // 取总长度的百分之20片段
        if (flowPnts.length > 0) {
            flowPnts.forEach((p, i) => {
                if (i == 0) {
                    path.moveTo(p.x, p.y);
                } else {
                    path.lineTo(p.x, p.y);
                }
            })
            ctx.lineWidth = lineWidth * .7
            const gradient = ctx.createLinearGradient(flowPnts[0].x, flowPnts[0].y, flowPnts[flowPnts.length - 1].x, flowPnts[flowPnts.length - 1].y)
            gradient.addColorStop(0, "rgb(0, 255, 127,0)")
            gradient.addColorStop(0.2, "rgb(0, 255, 127,0.01)")
            gradient.addColorStop(0.4, "rgb(0, 255, 127,0.04)")
            gradient.addColorStop(0.6, "rgb(0, 255, 127,0.3)")
            gradient.addColorStop(0.8, "rgb(0, 255, 127,0.6)")
            gradient.addColorStop(1, "rgb(0, 255, 127,.9)");
            ctx.strokeStyle = gradient;
            ctx.lineCap = this.lineCap;
            if (this.lineWidth > 0) ctx.stroke(path);
        }
    }

    override destroy() {
        super.destroy();
        this.startFeature?.off(Events.ROTATE, this._onTranslateStart)
        this.startFeature?.off(Events.TRANSLATE, this._onTranslateStart)
        this.startFeature?.on(Events.RESIZE, this._onTranslateStart)
        this.endFeature?.off(Events.ROTATE, this._onTranslateEnd)
        this.endFeature?.off(Events.TRANSLATE, this._onTranslateEnd)
        this.endFeature?.off(Events.RESIZE, this._onTranslateEnd)
        this.startFeature?.off(Events.DELETE, this._onDelete)
        this.endFeature?.off(Events.DELETE, this._onDelete)
    }
}